package com.yilizhiyou.service;

import com.yilizhiyou.algorithm.CollaborativeFiltering;
import com.yilizhiyou.algorithm.DecisionTreeRecommendation;
import com.yilizhiyou.entity.ScenicSpot;
import com.yilizhiyou.model.RecommendationResult;
import com.yilizhiyou.model.UserPreference;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 个性化行程推荐引擎
 * 融合协同过滤 + 决策树，并结合天气与“交通”因素做重排序
 */
@Service
public class RecommendationEngine {

    private final CollaborativeFiltering collaborativeFiltering;
    private final DecisionTreeRecommendation decisionTreeRecommendation;
    private final WeatherService weatherService;

    public RecommendationEngine(CollaborativeFiltering collaborativeFiltering,
                                DecisionTreeRecommendation decisionTreeRecommendation,
                                WeatherService weatherService) {
        this.collaborativeFiltering = collaborativeFiltering;
        this.decisionTreeRecommendation = decisionTreeRecommendation;
        this.weatherService = weatherService;
    }

    /**
     * 生成个性化推荐
     */
    public RecommendationResult recommend(UserPreference user,
                                          List<UserPreference> allUsers,
                                          List<ScenicSpot> allSpots,
                                          int topN) {
        // 1) 决策树基于用户偏好做第一轮过滤
        List<ScenicSpot> dtCandidates = decisionTreeRecommendation
            .recommendByDecisionTree(user, allSpots, Math.max(topN * 2, 10));

        // 2) 协同过滤基于相似用户/物品做补充
        List<ScenicSpot> cfUser = collaborativeFiltering
            .recommendByUserSimilarity(user, allUsers, allSpots, Math.max(topN, 6));
        List<ScenicSpot> cfItem = collaborativeFiltering
            .recommendByItemSimilarity(user, allUsers, allSpots, Math.max(topN, 6));

        // 合并去重为候选集
        LinkedHashMap<Long, ScenicSpot> candidateMap = new LinkedHashMap<>();
        for (ScenicSpot s : dtCandidates) candidateMap.putIfAbsent(s.getId(), s);
        for (ScenicSpot s : cfUser) candidateMap.putIfAbsent(s.getId(), s);
        for (ScenicSpot s : cfItem) candidateMap.putIfAbsent(s.getId(), s);

        List<ScenicSpot> candidates = new ArrayList<>(candidateMap.values());
        if (candidates.isEmpty()) candidates = new ArrayList<>(allSpots);

        // 3) 综合评分：个性化匹配 + 景区热度 + 天气适配 + 交通(模拟)
        Map<Long, Double> scores = new HashMap<>();
        for (ScenicSpot spot : candidates) {
            double score = 0.0;

            // 热度：使用rating简单近似
            score += safe(spot.getRating()) * 2.0; // 最高 ~10分

            // 价格贴合度
            if (spot.getPrice() != null) {
                double priceLevel = spot.getPrice() / 100.0;
                score += Math.max(0, 5 - Math.abs(priceLevel - user.getPricePreference())); // 0-5
            }

            // 天气因素（spot.weather 已在控制器初始化时填充）
            String cond = spot.getWeather();
            if (cond != null) {
                score += weatherAffinity(cond, user); // 0-3
            } else if (spot.getLongitude() != null && spot.getLatitude() != null) {
                // 如果不存在，临时实时拉取一份（防守型，避免空值）
                Map<String, Object> w = weatherService.getWeatherByCoordinates(spot.getLongitude(), spot.getLatitude());
                score += weatherAffinity(String.valueOf(w.get("condition")), user);
            }

            // 交通因素（此处用简单距离/顺路性近似：越靠近伊犁师范大学起点越好）
            // 起点经纬度：81.324, 43.944
            if (spot.getLongitude() != null && spot.getLatitude() != null) {
                double d = distanceKm(81.324, 43.944, spot.getLongitude(), spot.getLatitude());
                score += Math.max(0, 5 - Math.min(d / 20.0, 5)); // 距离越近越加分，最多5分
            }

            // 类型贴合
            score += typeAffinity(spot.getType(), user); // 0-5

            scores.put(spot.getId(), score);
        }

        // 4) 重排序并取TopN
        List<ScenicSpot> finalList = candidates.stream()
            .sorted((a, b) -> Double.compare(scores.getOrDefault(b.getId(), 0.0), scores.getOrDefault(a.getId(), 0.0)))
            .limit(topN)
            .collect(Collectors.toList());

        RecommendationResult result = new RecommendationResult(UUID.randomUUID().toString(), "个性化行程推荐", finalList);
        result.setAlgorithmType("CF+DecisionTree");
        result.setPersonalizationScore(estimatePersonalization(user, finalList));
        result.setEstimatedCost(finalList.stream().filter(s -> s.getPrice() != null).mapToDouble(ScenicSpot::getPrice).sum() * 1.0);
        result.setDescription("基于偏好/热度/天气/交通综合打分的Top" + topN + "行程");
        result.calculateTotalScore();
        return result;
    }

    private double weatherAffinity(String condition, UserPreference user) {
        if (condition == null) return 0.0;
        switch (condition) {
            case "晴": return 3.0;
            case "多云": return 2.5;
            case "小雨": return user.getActivityIntensity() <= 3 ? 1.5 : 0.5;
            case "中雨":
            case "大雨": return 0.5;
            case "雪": return 0.8;
            case "雾": return 0.8;
            default: return 1.0;
        }
    }

    private double typeAffinity(String type, UserPreference user) {
        if (type == null) return 0.0;
        switch (type) {
            case "自然风光": return Math.max(0, user.getNatureInterest());
            case "人文景观": return Math.max(0, user.getCulturalInterest());
            default: return Math.max(0, user.getHistoricalInterest());
        }
    }

    private double estimatePersonalization(UserPreference user, List<ScenicSpot> spots) {
        if (spots.isEmpty()) return 0.0;
        double sum = 0.0;
        for (ScenicSpot s : spots) {
            sum += typeAffinity(s.getType(), user);
        }
        return Math.min(100.0, (sum / spots.size()) * 20.0);
    }

    private double distanceKm(double lng1, double lat1, double lng2, double lat2) {
        double R = 6371.0; // km
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);
        double a = Math.sin(dLat/2) * Math.sin(dLat/2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLng/2) * Math.sin(dLng/2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        return R * c;
    }

    private double safe(Double v) { return v == null ? 0.0 : v; }
}

